SOURCE CODE of the Tennis Applet

 

/*******************************************************************************

 FILE         :   TennisApplet.java

 COPYRIGHT    :   DMALEX, 1999

 DESCRIPTION  :   Funny tennis

 PROGRAMMED BY:   Alex Fedosov

 CREATION DATE:   05/11/1999

 LAST UPDATE  :   19/11/1999

*******************************************************************************/

 

import java.awt.*;

import java.applet.*;

import java.util.*;

import java.net.*;

import java.io.*;

 

 

// base class for objects which we can move

abstract class MoveableObject

{

   MoveableObject(Dimension _size, Dimension _CourtWidth, int _posX, int _posY)

   {

      CourtWidth = _CourtWidth;

      size       = _size;

      pos        = new Point(_posX, _posY);

      origPos    = new Point(_posX, _posY);

   }

  

   // here we rule over the object's movement

   public abstract void direct(int direction);

   public abstract void show(Graphics g);

  

   // restore starting position

   public void reset(Graphics g)

   {

      pos.setLocation(origPos);

      show(g);

   }

 

   // check for collisions

   public boolean isIntersect(MoveableObject o)

   {

      return pos.x + size.width >= o.pos.x &&

             pos.x <= o.pos.x + o.size.width;

   }

     

   Dimension CourtWidth; // game area

   Dimension size;       // object size 

   Point     pos;        // current object position

   Point     origPos;    // starting position

}

 

 

// you know what does 'Racket' means in the game, eh? :-)

// so it's just self-descriptive class => no comments

class Racket extends MoveableObject

{

   Racket(Dimension _size, Dimension _CourtWidth, int _posX, int _posY)

   {

      super(_size, _CourtWidth, _posX, _posY);

      deltaX = 0;

   }

  

   public void reset(Graphics g)

   {

      deltaX = 0;

      g.clearRect(pos.x-1, pos.y, size.width+2, size.height);

      super.reset(g);

   }

  

   public void show(Graphics g)

   {

      if(deltaX == 0)

         {

         g.fillRect(pos.x, pos.y, size.width, size.height);

         return;

         }

     

      if(deltaX > 0)

         {

         // reduce time refresh but increase video-irregularities  ;-(

         // g.fillRect(pos.x+size.width, pos.y, 1, size.height);

         g.fillRect(pos.x+1, pos.y, size.width, size.height);

         g.clearRect(pos.x, pos.y, 1, size.height);

         }

      else

         {

         // g.fillRect(pos.x, pos.y, 1, size.height);

         g.fillRect(pos.x, pos.y, size.width, size.height);

         g.clearRect(pos.x + size.width, pos.y, 1, size.height);

         }

     

      pos.x += deltaX;

      if(pos.x + size.width >= CourtWidth.width || pos.x <= 0)

         {

         deltaX = -deltaX;

         pos.x += deltaX;

         }

   }

  

   public void direct(int direction)

   {

      deltaX = direction;

   }

  

   int deltaX;

}

 

 

 

// just self-descriptive class => no comments

class Ball extends MoveableObject

{

   Ball(Dimension _size, Dimension _CourtWidth, int _posX, int _posY, boolean _fSavePos)

   {

      super(_size, _CourtWidth, _posX, _posY);

      delta    = new Point(0, 0);

      randGen  = new Random(0);

      fSavePos = _fSavePos;

      savePos  = new Point(_posX, _posY);

   }

 

   int getNextRand()

   {

      // from range -2...2 except 0

      int r = (int)(randGen.nextFloat() * 3) + 1;

      if(r > 2)

         r = r - 5;

     

      return r;

   }

  

   public void direct(int direction)

   {

      if(direction == 0)

         delta = new Point(getNextRand(), getNextRand());

      else

         {

         delta.y = -delta.y;

         // adjust speed

         if(Math.abs(delta.x) == direction)

            delta.x = direction == 1 ? delta.x*2 : delta.x/2;

         }

   }

  

   public void reset(Graphics g)

   {

      if(fSavePos)

         return;

      

      delta = new Point(0, 0);

      g.clearRect(pos.x, pos.y, size.width, size.height);

      super.reset(g);

   }

  

   public void show(Graphics g)

   {

      if(fSavePos)

         {

         // in the netgame we show the ball based on data from server side

         if(savePos.x != pos.x)

            g.clearRect(savePos.x, savePos.y, size.width, size.height);

         g.fillOval(pos.x, pos.y, size.width, size.height);

         savePos.x = pos.x;

         savePos.y = pos.y;

         return;

         }

     

      if(delta.x == 0 || delta.y == 0)

         {

         // slow down refresh  ;-(

         // g.setColor(Color.red);

         g.fillOval(pos.x, pos.y, size.width, size.height);

         return;

         }

 

      g.clearRect(pos.x, pos.y, size.width, size.height);

     

      pos.x += delta.x;

      pos.y += delta.y;

     

      if(pos.x + size.width >= CourtWidth.width || pos.x <= 0)

         delta.x = -delta.x;

     

      g.fillOval(pos.x, pos.y, size.width, size.height);

   }

  

   Point   delta;

   Random  randGen;

   boolean fSavePos; // should we save previous position of the ball?

   Point   savePos;  // previous position

}

 

 

// main class

class CourtView extends Canvas implements Runnable

{

   Dimension CourtSize  = new Dimension(230, 300);

   Dimension racketSize = new Dimension(40, 5);

   Dimension ballSize   = new Dimension(6, 6);

  

   Racket playerOne = new Racket(racketSize, CourtSize, CourtSize.width/2 - racketSize.width/2, CourtSize.height - racketSize.height);

   Racket playerTwo = new Racket(racketSize, CourtSize, CourtSize.width/2 - racketSize.width/2, 0);

   Ball   ball      = new Ball(ballSize, CourtSize, CourtSize.width/2, CourtSize.height/2, false);

  

   NetSettingsDialog nDlg = null;  // dialog of the net-tuning

   ServerSocket srv       = null;  // server (==null at the client side)

   Socket       socket    = null;  // current connection

   OutputStream os        = null;  // data stream

   InputStream  is        = null;  // data stream

   int          fState    = 0;     // 0 - no requests, 1 - request for single play, 2 - request for netgame

   boolean      isNetGame = false; // what type of the game do we play now?

     

   int    delay = 0;       // wait for nothing 'delay' msec

   int    playerOnePoints; // score for 'our' side

   int    playerTwoPoints; // score for 'opponent' side

   Label  scoreBoard;      // score-board

 

  

   CourtView(Label _scoreBoard)

   {

      scoreBoard = _scoreBoard;

      resize(CourtSize);

      setBackground(Color.cyan);                 

   }

  

   public synchronized void Delay(int _delay)

   {

      try{

         switch(_delay)

            {

            case -1:

               Thread.sleep(1500);

               break;

              

            case 0:                             

               Thread.sleep(delay/10);

               // strange operation because of strange Thread.sleep(5)

               // behavior with magic parameter's value = 5

               for(int i = 0, j = 0; i < 10000 * delay; i++)

                  j++;

               break;

              

            default:

               delay = _delay;

               break;

            }

         }

      catch(Exception e)

         {

         }

   }

  

  

   void showMsg(String msg)

   {

      scoreBoard.setText(msg);

      Delay(-1); // generally user has very imperfect vision organs :-)

   }

  

 

   // here is the dedicated bug place!

   // I should use special synchronized class instead fState variable

   // but I have no time and motivation to do this...

   // ...all the more it still works and main programmers rule is:

   // "if it works don't touch it!" :-)

   public synchronized void start(boolean _isNetGame)

   {

      // what this capricious user wanna now? :-)

      fState = _isNetGame ? 2 : 1;

   }

 

  

   void closeConnection()

   {

      try{        

         if(os != null)

            {

            os.close();

            os = null;

            }

                 

         if(is != null)

            {

            is.close();

            is = null;

            }

              

         if(socket != null)

            {

            socket.close();           

            socket = null;

            }

              

         if(srv != null)

            {

            srv.close();

            srv = null;

            }

         }

      catch(Exception e)

         {

         os = null;

         is = null;

         socket = null;

         srv = null;

         }

   }

  

  

   boolean selectNetGame()

   {    

      if(nDlg == null)

         nDlg = new NetSettingsDialog();

     

      nDlg.show();

      if(nDlg.fCancel)

         return false;

     

      try{        

         closeConnection();

         showMsg("Looking for server...  ");

         socket = new Socket(InetAddress.getByName(nDlg.txtIPAddr.getText()), Integer.valueOf(nDlg.txtIPPort.getText()).intValue());

         os = socket.getOutputStream();

         is = socket.getInputStream();

         ball = new Ball(ballSize, CourtSize, CourtSize.width/2, CourtSize.height/2, true);        

         }

      catch(Exception e)

         {

         // so I'm server!

         try{           

            showMsg("Server not found. Create new server.");

            srv = new ServerSocket(Integer.valueOf(nDlg.txtIPPort.getText()).intValue(), 5);

            socket = srv.accept();

            os = socket.getOutputStream();

            is = socket.getInputStream();

            ball = new Ball(ballSize, CourtSize, CourtSize.width/2, CourtSize.height/2, false);

            }

         catch(Exception x)

            {

            closeConnection();

            showMsg("Network problems. Tune security in your browser");

            // "Tune security, tune security....."

            // .... or just force me to do 'signed applet'! :-)

            return false;

            }

         }           

      return true;

    }

                                 

   public void reset(Graphics g)

   {    

      g.clearRect(0, 0, CourtSize.width, CourtSize.height);

      ball.reset(g);

      playerOne.reset(g);

      playerTwo.reset(g);

      showMsg("You: " + String.valueOf(playerOnePoints) +

              " Opponent: " + String.valueOf(playerTwoPoints));

   }

  

   public synchronized void paint(Graphics g)

   {

      switch(fState)

         {

         case 0: break;

         case 1:

            {

            fState = 0;

            isNetGame = false;

            closeConnection(); // to release other side

            ball = new Ball(ballSize, CourtSize, CourtSize.width/2, CourtSize.height/2, false);

            showMsg("Single play against computer");

            playerTwoPoints = playerOnePoints = 0;

            reset(g);

            break;

            }

           

         case 2:

            {

            fState = 0;

            if(!selectNetGame())

               break;

           

            isNetGame = true;

            playerTwoPoints = playerOnePoints = 0;

            reset(g);

            break;

            }

         }

     

    

      if(isNetGame)

         dataExchange();

      else

         // computer's AI very simple and human-loved.... in the meanwhile :-)

         if(ball.pos.x > playerTwo.pos.x + playerTwo.size.width/2)

            playerTwo.direct(1);

         else

            playerTwo.direct(-1);

     

      // lady... sorry... player (server especially) first! :-)

      if(ball.delta.x == 0 && playerOne.deltaX != 0 && ((isNetGame && srv != null) || !isNetGame))

         ball.direct(0);

 

      // time to win? ...or lose?

      boolean fBallAtPlayerTwoSide = ball.pos.y <= playerTwo.size.height + 1;

      if(fBallAtPlayerTwoSide || ball.pos.y + ball.size.height >= ball.CourtWidth.height - playerOne.size.height - 1)

         if(ball.isIntersect(fBallAtPlayerTwoSide ? playerTwo : playerOne))

            if(fBallAtPlayerTwoSide  && ball.delta.x *  playerTwo.deltaX > 0 ||

               !fBallAtPlayerTwoSide && ball.delta.x *  playerOne.deltaX > 0)

               ball.direct(1); // speed up

            else

               ball.direct(2); // speed down

         else

            {

            checkScore(fBallAtPlayerTwoSide);

            reset(g);

            }

 

      // just refresh

      ball.show(g);

      playerOne.show(g);

      playerTwo.show(g);

   }

 

  

   void checkScore(boolean playerPoint)

   {

      if(playerPoint)

         playerOnePoints++;

      else

         playerTwoPoints++;

           

      if(playerOnePoints >= 20)

         {

         showMsg("You are winner!");

         playerTwoPoints = playerOnePoints = 0;

         return;

         }

      else              

         if(playerTwoPoints >= 20)

            {

            showMsg("You are loser!");

            playerTwoPoints = playerOnePoints = 0;

            return;

            }      

   }

  

   // reload the function to prevent undesirable view refresh

   public synchronized void update(Graphics g)

   {

      paint(g);

   }

  

   public synchronized void proceedKey(int key)

   {

      switch(key)

         {

         case Event.LEFT:  playerOne.direct(-1); break;

         case Event.RIGHT: playerOne.direct(1);  break;

         }

   } 

  

   // we can transfer the data byte for byte over the net but our view

   // area size could exceed 256 size limit! so we need data converter.

   int bytes2coord(byte one, byte two)

   {

      return (one+127) * 255 + two + 127;

   }

  

   Point coord2bytes(int coord)

   {

      Point ret = new Point();

      ret.x = coord / 255;

      ret.y = coord - ret.x * 255;

      ret.x -= 127;

      ret.y -= 127;

      return ret;

   }

  

   synchronized void dataExchange()

   {

      try{

         if(srv == null)

            {

            // I'm client

            byte buf[] = new byte[1];

            buf[0] = (byte)playerOne.deltaX;

            os.write(buf);

                         

            buf = new byte[5];

            is.read(buf);

            playerTwo.direct(-buf[0]);

            // I don't like 'getter/setter' here :-)

            ball.pos.x = ball.CourtWidth.width  - ball.size.width  - bytes2coord(buf[1], buf[2]);

            ball.pos.y = ball.CourtWidth.height - ball.size.height - bytes2coord(buf[3], buf[4]);

            }

         else

            {

            // I'm server

            byte buf[] = new byte[1];

            is.read(buf);

            playerTwo.direct(-buf[0]);

                    

            buf = new byte[5];

            buf[0] = (byte)(playerOne.deltaX);

            buf[1] = (byte)coord2bytes(ball.pos.x).x;

            buf[2] = (byte)coord2bytes(ball.pos.x).y;

            buf[3] = (byte)coord2bytes(ball.pos.y).x;

            buf[4] = (byte)coord2bytes(ball.pos.y).y;

            os.write(buf);

            }

         }

      catch(Exception e)

         {

         closeConnection();

         showMsg("Some problem during data transfer.");

         fState = 1;

         }

}

 

   public void run()

   {

      try{

         // main loop

         while(true)

            {

            this.repaint();

            Delay(0);           

            }

         }

      catch(Exception e)

         {

         showMsg("Sorry, there's some software problem. Bye.");

         }

   }

}

 

 

// to bring speed of the computer into accord with human's abilities

class speedRegulator extends Panel

{

   final int maxDelay   = 100;

   Scrollbar scrlSpeed  = new Scrollbar(Scrollbar.HORIZONTAL, 1, 10, 0, maxDelay + 10);

   Label     valueSpeed = new Label();

   CourtView cView;

   int       width;

   int       height;

 

   speedRegulator(CourtView _cView, int _width, int _height)

   {

      setLayout(new GridLayout(1, 2));

      add(scrlSpeed);

      add(valueSpeed);

      cView  = _cView;

      width  = _width;

      height = _height;

     

      // speed calibration (roughly)

      long msecBefore = System.currentTimeMillis();

      for(int i = 0; i < 10000000; i++) height = _height;

      long msecAfter = System.currentTimeMillis();

     

      if(msecAfter <= msecBefore)

         scrlSpeed.setValue(maxDelay); // max value

      else     

         scrlSpeed.setValue(Math.max((80 - ((int)(msecAfter - msecBefore))/20), 0));

     

      set();

   }

  

   void set()

   {

      valueSpeed.setText("Speed delay: " + String.valueOf(scrlSpeed.getValue()));

      cView.Delay(scrlSpeed.getValue());

   }

  

   public boolean handleEvent(Event e)

   {    

      if(e.target.equals(scrlSpeed))

         {

         set();

         return true;

         }

     

      return super.handleEvent(e);

   }

  

   public Dimension preferredSize()

   {

      return new Dimension(width, height);

   }

}

 

 

 

// wanna play with the REAL opponent? Select it!

class NetSettingsDialog extends Dialog

{

   Button btnOK        = new Button("  OK  ");

   Button btnCancel    = new Button("Cancel");

   TextField txtIPAddr = new TextField("1.1.1.1");

   TextField txtIPPort = new TextField("8000");

   boolean fCancel     = true;

  

   NetSettingsDialog()

   {

      super(new Frame(), "Network game setting", true);

      setLayout(new GridLayout(4,2));

     

      try{

         add(new Label("Your IP address:"));

         add(new Label(InetAddress.getLocalHost().toString()));

         }

      catch(Exception x)

         {

         // apparently you have very strange IP

         // and how are you going to play network game in this case?

         // really I don't care! :-)

         }

     

      Label lblOppon = new Label("IP address of your opponent:");

      add(lblOppon);

      add(txtIPAddr);

      add(new Label("IP port of your opponent:"));

      add(txtIPPort);     

     

      Panel btns = new Panel();

      btns.setLayout(new FlowLayout());

      btns.add(btnOK);

      btns.add(btnCancel);

      add(btns);

      // It's pity but it doesn't work ;-( So use predefined settings instead.

      // int h, w = lblOppon.getFontMetrics(lblOppon.getFont()).stringWidth("IP address of your opponent:  111.111.111.111");

      // h = lblOppon.getFontMetrics(lblOppon.getFont()).getHeight();

      // resize(w + 10, h * 4 + 10);

      resize(350, 150);

   }

  

   public boolean handleEvent(Event e)

   {

      if(e.id == Event.WINDOW_DESTROY)

         {

         hide();

         return true;

         }

     

      return super.handleEvent(e);

   }

     

   public boolean keyDown(Event e, int key)

   {    

      if(key == Event.ENTER || key == Event.ESCAPE)

         {

         fCancel = key == Event.ESCAPE;

         hide();

         return true;

         }

      return false;

   }

  

   public boolean action(Event e, Object o)

   {

      if(e.target.equals(btnOK) || e.target.equals(btnCancel))

         {

         fCancel = e.target.equals(btnCancel);

         hide();

         return true;

         }

     

      return false;

   }

  

}

 

 

 

public class TennisApplet extends Applet

{

   /**

    * The entry point for the applet.

    */

   public void init()

   {    

      initForm();

   }

 

   // some useful controls

   Label     scoreBoard = new Label("Press 'Restart' button to begin......");

   Button    btnStart   = new Button("Restart");

   Button    btnNetGame = new Button("NetGame");

   CourtView cView      = new CourtView(scoreBoard);

   speedRegulator speedReg = new speedRegulator(cView, 200, 20);

   Thread    threadPic  = null; // game's thread

     

   public boolean keyDown(Event e, int key)

   {    

      cView.proceedKey(key);

      return false;

   }

  

  

   public void start()

   {

      if(threadPic == null)

      {

         threadPic = new Thread(cView);

         threadPic.start();

      }

   }

  

  

   public void stop()

   {

      if(threadPic != null)

      {

         threadPic.stop();

         threadPic = null;

      }

   } 

  

   public boolean action(Event e, Object o)

   {    

      if(e.target.equals(btnStart))

         {

         cView.start(false);

         return true;

         }

      else

         if(e.target.equals(btnNetGame))

            {

            cView.start(true);

            return true;

            }

  

      return false;

   }

 

   /**

    * Intializes values for the applet and its components

    */

   void initForm()

   {

      setBackground(Color.lightGray);

      setForeground(Color.black);

      setLayout(new FlowLayout());

      add(btnStart);

      add(btnNetGame);

      add(scoreBoard);

      add(cView);

      add(speedReg);

   }

}